package com.dh.spring.client.http;

import com.dh.spring.client.bootstrap.FileCoreServiceHandler;
import com.dh.spring.client.bootstrap.GlobalNIOClientBootstrap;
import com.dh.spring.client.bootstrap.GlobalNIOClientListenerDelegate;
import com.dh.spring.client.bootstrap.ServerSocketChannelHttpRequestHandler;
import com.dh.spring.client.file.common.FileCheckRunnable;
import com.dh.spring.client.file.common.SendEntity;
import com.dh.spring.client.file.download.SocketDecorate;
import com.dh.spring.common.BasicConstant;
import com.dh.spring.common.EPlatform;
import com.dh.spring.common.HttpResponse;
import com.dh.spring.common.OSinfo;
import com.dh.spring.util.BasicUtil;
import com.dh.spring.util.LocalTime;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Auther: YSFY
 * @Date: 2020-09-02 21:52
 * @Pacage_name: com.dh.spring.client.http
 * @Project_Name: arthas-client
 * @Description: 文件http上传请求处理类
 */

@Slf4j
@SuppressWarnings("all")
public final class HttpUploadHandler {
    private static AtomicInteger channelIndex = new AtomicInteger(1);

    public static HttpResponse httpUploadHandler(RequestData requestData, HttpResponse httpResponse) throws InterruptedException, ExecutionException {
        //TODO 文件上传校http请求路径校验
        if(StringUtil.isNullOrEmpty(requestData.getClientUploadPath()))
            httpResponse.getBodyData().append("文件客户端上传路径不能为空,");
        if(StringUtil.isNullOrEmpty(requestData.getServerRestorePath()))
            httpResponse.getBodyData().append("文件服务端上传存储路径不能为空,");

        if(!StringUtil.isNullOrEmpty(httpResponse.getBodyData().toString())) {
            httpResponse.getBodyData().toString().substring(0, httpResponse.getBodyData().toString().lastIndexOf(","));
            return httpResponse;
        }
        return uploadHandler(requestData, httpResponse);
    }

    private static HttpResponse uploadHandler(RequestData requestData, HttpResponse httpResponse) throws InterruptedException, ExecutionException {
        List<Callable<FileCheckRunnable.FileCheckResult>> list = new ArrayList<>(Runtime.getRuntime().availableProcessors());
        if(!"".equals(requestData.getData())) {
            String fileNames = requestData.getData();

            if(fileNames.contains("，") || fileNames.contains(",")) { //TODO 将文件名称集合中的所有中文逗号替换为英文逗号
                fileNames = fileNames.replaceAll("，", ",");
            }
            if(fileNames.endsWith(",") || fileNames.endsWith("，")) { //TODO 去掉文件名集合最后一个文件后面的逗号
                fileNames = fileNames.substring(0, fileNames.lastIndexOf(","));
            }

            if(fileNames.contains(",")) { //TODO 待上传不止一个文件
                String[] files = fileNames.split(",");
                int count = 0;
                for(String fileName : files) {
                    if(judgeCurrentFileIsUploading(fileName)) {
                        httpResponse.getBodyData().append(fileName + "操作中，请稍后再试,");
                    } else {
                        /*if(count == Runtime.getRuntime().availableProcessors()) {
                            if(result.endsWith(",")) {
                                result = result.substring(0, result.lastIndexOf(","));
                            }
                            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 文件操作个数以达到最大支持数，将暂停部分文件上传，正在进行的上传文件为: file = {fileName = {}, fileUploadMaxCount = {}}", result, list.size());
                            return "文件操作个数以达到最大支持个数，将暂停部分文件上传";
                        }*/
                        list.add(new FileCheckRunnable(fileName, requestData));
                        count++;
                    }
                }
            } else { //TODO 待上传只有一个文件
                if(!judgeCurrentFileIsUploading(fileNames)) {
                    list.add(new FileCheckRunnable(fileNames, requestData));
                } else {
                    httpResponse.getBodyData().append(fileNames + "操作中，请稍后再试,");
                }
            }
        }

        if(list.isEmpty()) {
            httpResponse.getBodyData().append("待上传的文件List为空，无法进行上传");
            return httpResponse;
        }
        List<Future<FileCheckRunnable.FileCheckResult>> resultHttpResponses = GlobalNIOClientBootstrap.executorService.invokeAll(list);
        if(!resultHttpResponses.isEmpty()) {
            for(Future<FileCheckRunnable.FileCheckResult> future : resultHttpResponses) {
                while (!future.isDone())
                   continue;
                if(future.get().isHandler()) {
                    log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 文件名称: fileName = {} http上传请求处理完成,稍后将进行上传操作", future.get().getFileName());
                    httpResponse.getBodyData().append(future.get().getResult());
                }
            }
        }
        if(httpResponse.getBodyData().toString().endsWith(","))
            httpResponse.getBodyData().toString().substring(0, httpResponse.getBodyData().toString().lastIndexOf(","));
        return httpResponse;
    }

    //TODO --> 此处判断当前内存中有无正在执行上传的文件的操作
    private static boolean judgeCurrentFileIsUploading(String fileName) {
        if(ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.UPLOAD).isEmpty()
            && ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.DOWNLOAD).isEmpty())
            return false;

        //TODO --> 正在上传的任务队列: 当同一文件正处于上传时，将禁止覆盖上传
        LinkedBlockingQueue uploadArrayBlockingQueue = ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.UPLOAD);
        Object[] obj = uploadArrayBlockingQueue.parallelStream().filter(q -> {
            if((((FileExecStatus) q).getStatus() == 1 && ((FileExecStatus) q).getFileName().equals(fileName)))
                return true;

            return false;
        }).toArray();
        if(obj.length > 0) {
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前文件: fileName = {}上传中，禁止重复上传", fileName);
            return true;
        }

        //TODO --> 正在下载的任务队列: 当同一文件正处于下载时,将禁止下载时覆盖上传
        LinkedBlockingQueue downloadArrayBlockingQueue = ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.DOWNLOAD);
        obj = downloadArrayBlockingQueue.parallelStream().filter(q -> {
            if((((FileExecStatus) q).getStatus() == 1 && ((FileExecStatus) q).getFileName().equals(fileName)))
                return true;

            return false;
        }).toArray();
        if(obj.length > 0) {
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前文件: fileName = {}下载中，禁止下载时覆盖上传", fileName);
            return true;
        }

        return false;
    }

    public static FileCheckRunnable.FileCheckResult executeFileUpload(String fileName, RequestData requestData, FileCheckRunnable.FileCheckResult fileCheckResult) {
        SelectionKey selectionKey = null;
        fileCheckResult.setHandler(true); //TODO --> 设置当前线程处理成功
        try {
            FileExecStatus fileExecStatus = new FileExecStatus();
            fileExecStatus.setFileName(fileName);
            fileExecStatus.setFileExec(requestData.getExec());
            fileExecStatus.setStatus(1);
            ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.UPLOAD).add(fileExecStatus);
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 成功加入待上传文件: fileName = {}, 当前上传队列中待上传文件数: fileUploadCount = {}", fileName, ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.UPLOAD).size());
            selectionKey = getSocketChannelUpload(fileName, requestData, fileExecStatus);
            if(null == selectionKey) {
                fileCheckResult.setResult("[ " + fileName + " ] 不存在或未指定文件上传来源 <-- \r\n");
                ServerSocketChannelHttpRequestHandler.fileExecStatusMap.get(BasicConstant.UPLOAD).remove(fileExecStatus);
                return fileCheckResult;
            }
            ((SocketChannel) selectionKey.channel()).connect(new InetSocketAddress(InetAddress.getByName(GlobalNIOClientListenerDelegate.SOCKET_REMOTE_IP), GlobalNIOClientListenerDelegate.SOCKET_REMOTE_PORT));
            fileCheckResult.setResult("[ " + fileName + " ] 校验成功,可执行上传 <-- \r\n");
            return fileCheckResult;
        } catch (IOException e) {
            FileCoreServiceHandler.closeSocketChannel(selectionKey, null, "HttpUploadHandler"," 文件上传连接服务端异常, error = " + e.getMessage(), BasicConstant.UPLOAD, fileName, 0);
            fileCheckResult.setResult("[ " + fileName + " ] 文件上传连接服务端异常, error = " + e.getMessage());
            return fileCheckResult;
        }
    }

    private static SelectionKey getSocketChannelUpload(String fileName, RequestData requestData, FileExecStatus fileExecStatus) throws IOException {
        log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前待上传的文件信息为: file = {fileName = {}, fileLength = {}}", fileName, fileName.getBytes().length);

        if (OSinfo.getOSname().toString().equals(EPlatform.Windows.toString()) && !requestData.getClientUploadPath().endsWith("\\")) //TODO --> windows系统
            requestData.setClientUploadPath(requestData.getClientUploadPath() + "\\");
        if (OSinfo.getOSname().toString().equals(EPlatform.Mac_OS_X.toString()) && !requestData.getClientUploadPath().endsWith("/"))  //TODO --> mac系统
            requestData.setClientUploadPath(requestData.getClientUploadPath() + "/");
        if (OSinfo.getOSname().toString().equals(EPlatform.Linux.toString()) && !requestData.getClientUploadPath().endsWith("/"))  //TODO --> Linux系统
            requestData.setClientUploadPath(requestData.getClientUploadPath() + "/");

        //TODO --> 判断上传是否是可移动磁盘上传
        String isUploadRemove = requestData.getIsUploadRemove();
        if (!"".equals(isUploadRemove) && "1".equals(isUploadRemove)) { //TODO 从可移动磁盘上传
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前待上传的文件来源为可移动磁盘");
            return removeUpload(fileName, fileExecStatus, requestData);
        } else if(!"".equals(isUploadRemove) && "0".equals(isUploadRemove)) {
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前待上传的文件来源为本地磁盘");
            return localUpload(fileName, fileExecStatus, requestData);
        } else {
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 文件上传http请求未指定文件上传存储来源,上传失败");
            return null;
        }
    }

    //TODO --> 可移动磁盘上传(递归遍历文件对象，直到找到符合上传路径的文件名称)
    private static SelectionKey removeUpload(String fileName, FileExecStatus fileExecStatus, RequestData requestData) throws IOException {
        File[] roots = File.listRoots();
        for (File file : roots) {
            if(file.isFile())
                continue;
            file = getFileWithUploadPath(fileName, file, requestData);
            if(null == file)
                return null;
            return decorateFileUploadSocketChannel(fileName, file, requestData, fileExecStatus);
        }

        return null;
    }

    private static File getFileWithUploadPath(String fileName, File file, RequestData requestData) {
        File[] removeFiles = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if((requestData.getClientUploadPath() + fileName).contains(dir.getPath() + (dir.getPath().equals("/")?"":"/")+ name))
                    return true;
                return false;
            }
        });
        if(removeFiles.length > 0) {
            for(File removeFile : removeFiles) {
                if(removeFile.isFile() && (requestData.getClientUploadPath() + fileName).equals(removeFile.getPath())) {
                    return removeFile;
                } else if(!removeFile.isFile()) {
                    return getFileWithUploadPath(fileName, removeFile, requestData);
                }

            }
        }

        return null;
    }

    //TODO --> 本地磁盘上传
    private static SelectionKey localUpload(String fileName, FileExecStatus fileExecStatus, RequestData requestData) throws IOException {
        File file = new File(requestData.getClientUploadPath() + fileName);
        if(!file.exists()) {
            log.info("[" + LocalTime.formatDate(LocalDateTime.now()) + "] HttpUploadHandler | --> 当前待上传的文件不存在,请确保上传的文件存在: file = {fileName = {}, filePath = {}}", fileName, file.getPath());
            return null;
        }

        return decorateFileUploadSocketChannel(fileName, file, requestData, fileExecStatus);
    }

    //TODO --> 客户端封装文件上传请求socket连接
    private static SelectionKey decorateFileUploadSocketChannel(String fileName, File file, RequestData requestData, FileExecStatus fileExecStatus) throws IOException {
        //TODO --> 1、创建文件socketChannel上传通道
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        SocketDecorate.getInstance(socketChannel.socket()).setCustomKeepAlive(true).setCustomTcpNoDelay(false).setSendBuffer(2048);;
        GlobalNIOClientBootstrap.selector.wakeup();
        SelectionKey selectionKey = socketChannel.register(GlobalNIOClientBootstrap.selector, SelectionKey.OP_CONNECT);

        //TODO --> 2、获取文件上传http请求数据
        byte[] fileNameBytes = fileName.getBytes(Charset.forName("UTF-8"));//TODO 文件名称字节数组大小(int)数据 4
        byte[] execBytes = requestData.getExec().getBytes(Charset.forName("UTF-8"));//TODO 操作命令名称字节数组大小(int)数据 4
        if(!requestData.getServerRestorePath().endsWith("/")) //TODO --> 文件上传服务端存储路径，由于不知道文件服务端服务运行于什么操作系统所以无法判断路径，交由文件服务端判断
            requestData.setServerRestorePath(requestData.getServerRestorePath() + "/");
        byte[] pathBytes = requestData.getServerRestorePath().getBytes(Charset.forName("UTF-8"));//TODO --> 文件上传服务器存储路径字节数组大小(int)数据 4

        //TODO --> 3、封装文件上传请求流数据以及实体类交由服务端校验
        ByteBuffer sendFileBuffer = ByteBuffer.allocateDirect(4 + 8 + 4 + 4 + 4 + fileNameBytes.length + execBytes.length + pathBytes.length);
        sendFileBuffer.put(BasicUtil.intToBytes(fileName.length()));// TODO 文件名称长度数据 4(发送文件名称用于判断服务端判断文件是否存在)
        sendFileBuffer.put(BasicUtil.longToBytes(file.length())); //TODO 文件大小long数据 目前可以不用到，给予默认 8
        sendFileBuffer.put(BasicUtil.intToBytes(fileNameBytes.length));
        sendFileBuffer.put(BasicUtil.intToBytes(execBytes.length));
        sendFileBuffer.put(BasicUtil.intToBytes(pathBytes.length));
        sendFileBuffer.put(fileNameBytes); //TODO 文件名称字节数组数据
        sendFileBuffer.put(execBytes);//TODO 命令内容数据 (即DOWNLOAD.getBytes() 和 UPLOAD.getBytes())
        sendFileBuffer.put(pathBytes);//TODO 路径内容数据
        SendEntity sendEntity = new SendEntity();
        sendEntity.setChannelName("[ " + fileName + " ] 文件上传通道 --- " + channelIndex.getAndIncrement());
        sendEntity.setHandlerName(BasicConstant.FILE_CORE_SERVICE_HANDLER);
        sendEntity.setByteBuffer(sendFileBuffer);
        sendEntity.setFileName(fileName);
        sendEntity.setExec(requestData.getExec());
        sendEntity.setPath(requestData.getClientUploadPath());
        sendEntity.setFileExecStatus(fileExecStatus);

        //TODO --> 4、上传文件信息实体注入当前可选键
        selectionKey.attach(sendEntity); //TODO
        GlobalNIOClientBootstrap.fileChannelsMap.put(selectionKey, new FileInputStream(file).getChannel());//TODO 创建待上传文件FileChannel
        return selectionKey;
    }
}
